package org.brakepedal.server.support.bluetooth;

import jakarta.annotation.Resource;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.brakepedal.server.support.exception.IOExceptionWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;

import javax.microedition.io.Connector;
import javax.microedition.io.InputConnection;
import java.io.Closeable;
import java.io.IOException;
import java.io.InputStream;
import java.util.IdentityHashMap;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;

/**
 * Date: 2024/4/1 13:00
 *
 * @author Huanyu Mark
 */
@Lazy
@Slf4j
@Component
@Scope("prototype")
public class BtDecodeBroker implements Closeable {
    private final BtService service;

    private InputConnection connection;

    private InputStream inputStream;

    private ExecutorService taskExecutor;

    private final Map<Class<? extends BtFrame>, FrameHandler<?>> frameHandlerMap = new IdentityHashMap<>();

    private CompletableFuture<?> decodeLoop;

    private BtFrameDecoder btFrameDecoder;

    @Autowired
    public void setBtFrameDecoder(@Value("${bluetooth.decode.abort-second}") Integer abortSecond, ScheduledThreadPoolExecutor scheduler) {
        this.btFrameDecoder =  new DefaultBtFrameDecoder(abortSecond,scheduler);
        this.taskExecutor = scheduler;
    }

    public BtDecodeBroker(BtService service) {
        this.service = service;
    }
    public BtDecodeBroker open() throws IOException {
//        connection = ((InputConnection) Connector.open(String.format("btspp://%s:%s", device.getAddress(), 1)));
        connection = ((InputConnection) Connector.open(service.getConnectionUrl()));
        inputStream = connection.openInputStream();
        decodeLoop = CompletableFuture.runAsync(()->{
            while (true) {
                BtFrame frame;
                try {
                    frame = btFrameDecoder.decode(inputStream);
                    log.info("decode frame {}",frame);
                } catch (IOException e) {
                    throw new IOExceptionWrapper(e);
                }
                @SuppressWarnings("unchecked")
                var frameHandler = (FrameHandler<BtFrame>) frameHandlerMap.get(frame.getClass());
                if(frameHandler == null) {
                    log.warn("No Matched FrameHandler: '{}'", frame.getClass());
                    return;
                }
                frameHandler.handle(frame);
            }
        }, taskExecutor);
        return this;
    }

    @SuppressWarnings("unchecked")
    public void registerFrameHandler(ExplicitFrameHandler<?> frameHandler){
        var frameType = frameHandler.getFrameType();
        if(frameType == null) {
            throw new IllegalArgumentException("frameType is null. FrameHandler class: "+frameHandler.getClass());
        }
        registerFrameHandler(((Class<BtFrame>) frameType), ((FrameHandler<BtFrame>) frameHandler));
        var old = frameHandlerMap.put(frameType, frameHandler);
        if(old != null) {
            throw new IllegalArgumentException("frameType '"+frameType+"' is already registered. FrameHandler class: "+frameHandler.getClass());
        }
    }

    public <F extends BtFrame> void registerFrameHandler(Class<F> frameType, FrameHandler<F> frameHandler) {
        var old = frameHandlerMap.put(frameType, frameHandler);
        if(old != null) {
            throw new IllegalArgumentException("frameType '"+frameType+"' is already registered. FrameHandler class: "+frameHandler.getClass());
        }
    }

    @Override
    public void close() throws IOException {
        if(decodeLoop != null) {
            decodeLoop.cancel(true);
        }
        if(inputStream != null) {
            inputStream.close();
        }
        if(connection != null) {
            connection.close();
        }
        if(btFrameDecoder instanceof Closeable closeable) {
            closeable.close();
        }
    }
}
